package com.jakewharton.rxbinding4.component;

import static com.jakewharton.rxbinding4.internal.Functions.PREDICATE_ALWAYS_TRUE;
import static com.jakewharton.rxbinding4.internal.Preconditions.checkNotNull;

import com.jakewharton.rxbinding4.InitialValueObservable;

import io.reactivex.rxjava3.annotations.CheckReturnValue;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Predicate;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentTreeObserver;
import ohos.multimodalinput.event.KeyEvent;
import ohos.multimodalinput.event.TouchEvent;

/**
 * Static factory methods for creating {@linkplain Observable observables} and {@linkplain Consumer
 * actions}* for {@link Component}.
 */
public final class RxComponent {
    /**
     * Create an observable which emits on {@code view} attach events. The emitted value is
     * unspecified and should only be used as notification.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<Object> bounds(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentBoundObservable(view, true);
    }

    /**
     * Create an observable of attach and detach events on {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<ComponentAttachEvent> boundEvents(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentAttachEventObservable(view);
    }

    /**
     * Create an observable which emits on {@code view} detach events. The emitted value is
     * unspecified and should only be used as notification.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<Object> unBounds(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentBoundObservable(view, false);
    }

    /**
     * Create an observable which emits on {@code view} click events. The emitted value is
     * unspecified and should only be used as notification.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     * <p>
     * <em>Warning:</em> The created observable uses {@link Component#setClickedListener} to observe
     * clicks. Only one observable can be used for a view at a time.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<Object> clicks(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentClickObservable(view);
    }

    /**
     * Create an observable of booleans representing the focus of {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     * <p>
     * <em>Warning:</em> The created observable uses {@link Component#setFocusChangedListener} to observe
     * focus change. Only one observable can be used for a view at a time.
     * <p>
     * <em>Note:</em> A value will be emitted immediately on subscribe.
     *
     * @param view view
     * @return the initial value observable
     */
    @CheckReturnValue
    @NonNull
    public static InitialValueObservable<Boolean> focusChanges(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentFocusChangeObservable(view);
    }

    /**
     * Create an observable which emits on {@code view} globalLayout events. The emitted value is
     * unspecified and should only be used as notification.
     * <p></p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     * <p>
     * <em>Warning:</em> The created observable uses {@link
     * ComponentTreeObserver#addTreeLayoutChangedListener}* to observe global layouts. Multiple observables
     * can be used for a view at a time.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<Object> treeLayouts(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentTreeObserverTreeLayoutObservable(view);
    }

    /**
     * Create an observable which emits on {@code view} layout changes. The emitted value is
     * unspecified and should only be used as notification.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<Object> layoutChanges(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentLayoutRefreshObservable(view);
    }

    /**
     * Create an observable which emits on {@code view} long-click events. The emitted value is
     * unspecified and should only be used as notification.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     * <p>
     * <em>Warning:</em> The created observable uses {@link Component#setLongClickedListener} to observe
     * long clicks. Only one observable can be used for a view at a time.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<Object> longClicks(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentLongClickObservable(view);
    }

    /**
     * Create an observable of scroll-change events for {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<ComponentScrollChangeEvent> scrollEvents(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentScrollEventObservable(view);
    }

    /**
     * Create an observable of touch events for {@code view}.
     * <p>
     * <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
     * object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
     * on a different thread). If you want to cache or delay reading the items emitted then you must
     * map values through a function to create a copy.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     * <p>
     * <em>Warning:</em> The created observable uses {@link Component#setTouchEventListener} to observe
     * touches. Only one observable can be used for a view at a time.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<TouchEvent> touches(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentTouchObservable(view, PREDICATE_ALWAYS_TRUE);
    }

    /**
     * Create an observable of touch events for {@code view}.
     * <p>
     * <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
     * object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
     * on a different thread). If you want to cache or delay reading the items emitted then you must
     * map values through a function to create a copy.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     * <p>
     * <em>Warning:</em> The created observable uses {@link Component#setTouchEventListener} to observe
     * touches. Only one observable can be used for a view at a time.
     *
     * @param view    view
     * @param handled Predicate invoked with each value to determine the return value of the underlying {@link Component.TouchEventListener}.
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<TouchEvent> touches(
            @NonNull Component view, @NonNull Predicate<? super TouchEvent> handled) {
        checkNotNull(view, "view == null");
        checkNotNull(handled, "handled == null");
        return new ComponentTouchObservable(view, handled);
    }

    /**
     * Create an observable of key events for {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     * <em>Warning:</em> The created observable uses {@link Component#setKeyEventListener} to observe
     * key events. Only one observable can be used for a view at a time.
     *
     * @param view view
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<KeyEvent> keys(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return new ComponentKeyObservable(view, PREDICATE_ALWAYS_TRUE);
    }

    /**
     * Create an observable of key events for {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     * <em>Warning:</em> The created observable uses {@link Component#setKeyEventListener} to observe
     * key events. Only one observable can be used for a view at a time.
     *
     * @param view    view
     * @param handled Predicate invoked each occurrence to determine the return value of the underlying {@link Component.KeyEventListener}.
     * @return the observable
     */
    @CheckReturnValue
    @NonNull
    public static Observable<KeyEvent> keys(@NonNull Component view, @NonNull Predicate<? super KeyEvent> handled) {
        checkNotNull(view, "view == null");
        checkNotNull(handled, "handled == null");
        return new ComponentKeyObservable(view, handled);
    }

    /**
     * An action which sets the clickable property of {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the consumer
     */
    @CheckReturnValue
    @NonNull
    public static Consumer<? super Boolean> clickable(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return view::setClickable;
    }

    /**
     * An action which sets the enabled property of {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the consumer
     */
    @CheckReturnValue
    @NonNull
    public static Consumer<? super Boolean> enabled(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return view::setEnabled;
    }

    /**
     * An action which sets the pressed property of {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the consumer
     */
    @CheckReturnValue
    @NonNull
    public static Consumer<? super Boolean> pressed(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return view::setPressState;
    }

    /**
     * An action which sets the selected property of {@code view}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the consumer
     */
    @CheckReturnValue
    @NonNull
    public static Consumer<? super Boolean> selected(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return view::setSelected;
    }

    /**
     * An action which sets the visibility property of {@code view}. {@code false} values use
     * {@code View.GONE}.
     * <p>
     * <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
     * to free this reference.
     *
     * @param view view
     * @return the consumer
     */
    @CheckReturnValue
    @NonNull
    public static Consumer<? super Integer> visibility(@NonNull Component view) {
        checkNotNull(view, "view == null");
        return view::setVisibility;
    }

    private RxComponent() {
        throw new AssertionError("No instances.");
    }
}
